﻿// //  ********************************************************************************
// //  Copyright (C) 2016 Sebastian Kenter. All rights reserved.
// // 
// //  Name    : PhotonServerProject | PhotonServerFramework | ServerPeer.cs
// //  Created : 2016 - 03 - 21 11:46
// //  Modified: 2016 - 04 - 18 09:38
// //  ********************************************************************************

using System;
using System.Collections.Generic;
using System.Net;
using ExitGames.Logging;
using NHibernate;
using Photon.SocketServer;
using Photon.SocketServer.ServerToServer;
using PhotonHostRuntimeInterfaces;
using PhotonServerCommon.Entities.Domain;
using PhotonServerCommon.Enums;
using PhotonServerCommon.Messages;
using PhotonServerCommon.Messages.Helper;
using PhotonServerFramework.Implementation.Configuration;
using PhotonServerFramework.Implementation.Database;
using PhotonServerFramework.Implementation.Telegram;
using PhotonServerFramework.Interfaces.Application;
using PhotonServerFramework.Interfaces.Configuration;
using PhotonServerFramework.Interfaces.Handler;
using PhotonServerFramework.Interfaces.Server;
using PhotonServerFramework.Interfaces.Telegram;

namespace PhotonServerFramework.Implementation.Server
{
    public class ServerPeer : ServerPeerBase, IServerPeer
    {
        public ILogger Logger { get; }
        public Guid ServerPeerId { get; } = Guid.NewGuid();
        public IPeerData ServerPeerData { get; }

        private readonly IServerApplication _server;

        private readonly IServerHandlerRegistry _serverHandlerRegistry;

        public ServerPeer(IRpcProtocol protocol, IPhotonPeer unmanagedPeer, ILogger logger,
                          IServerHandlerRegistry serverHandlerRegistry, IServerApplication server)
            : base(protocol, unmanagedPeer)
        {
            _serverHandlerRegistry = serverHandlerRegistry;
            Logger = logger;
            _server = server;
            ServerPeerData = new PeerData
                             {
                                 PeerId = ServerPeerId,
                                 LocalEndPoint =
                                     new IPEndPoint(IPAddress.Parse(unmanagedPeer.GetLocalIP()),
                                     unmanagedPeer.GetLocalPort()),
                                 RemoteEndPoint =
                                     new IPEndPoint(IPAddress.Parse(unmanagedPeer.GetRemoteIP()),
                                     unmanagedPeer.GetRemotePort()),
                                 ConnectionId = unmanagedPeer.GetConnectionID(),
                                 ApplicationId = _server.Configuration.Application,
                                 ClientVersion = null,
                                 NetworkProtocol = NetworkProtocol.ToString(),
                                 PhotonProtocol = protocol.ProtocolType.ToString(),
                                 PeerType = unmanagedPeer.GetPeerType().ToString()
                             };
        }

        public void ConnectPeer()
        {
            if (Connected && LocalPort != _server.Configuration.MasterTcpPort)
            {
                SendTelegram(new RequestTelegram
                             {
                                 TelegramCode = (byte) OperationCodes.System,
                                 TelegramSubCode = (int) OperationSubCodes.RegisterSubServer,
                                 TelegramParameters = new Dictionary<byte, object>
                                                      {
                                                          {
                                                              (byte) ParameterCodes.RegisterSubServerRequestMessage,
                                                              MessageSerializer.SerializeMessage(
                                                                  new RegisterSubServerRequest
                                                                  {
                                                                      ServerPeerId =
                                                                          ServerPeerId
                                                                          .ToString()
                                                                  })
                                                          }
                                                      }
                             });
            }
        }

        public void DisconnectPeer(string server)
        {
            Logger.InfoFormat("Disconnecting server peer with Guid = {0} requested by server {1}",
                ServerPeerId.ToString(),
                server);

            using (ISession session = SessionManager.OpenSession())
            {
                using (ITransaction transaction = session.BeginTransaction())
                {
                    try
                    {
                        ServerPeerT speer =
                            session.QueryOver<ServerPeerT>()
                                .Where(c => c.LocalPeerId == ServerPeerId.ToString())
                                .SingleOrDefault();
                        speer.Connected = 0;
                        speer.Tsn += 1;
                        session.Update(speer);
                        transaction.Commit();
                    }
                    catch (Exception ex)
                    {
                        Logger.ErrorFormat("Operation failed with: {0}", ex.Message);
                        transaction.Rollback();
                    }
                }

                using (ITransaction transaction = session.BeginTransaction())
                {
                    try
                    {
                        ServerPeerT speer =
                            session.QueryOver<ServerPeerT>()
                                .Where(c => c.LocalPeerId == ServerPeerId.ToString())
                                .SingleOrDefault();
                        speer.Connected = 0;
                        speer.Tsn += 1;
                        session.Delete(speer);
                        transaction.Commit();
                    }
                    catch (Exception ex)
                    {
                        Logger.ErrorFormat("Operation failed with: {0}", ex.Message);
                        transaction.Rollback();
                    }
                }

                session.Close();
            }
        }

        protected override void OnOperationRequest(OperationRequest operationRequest, SendParameters sendParameters)
        {
            HandlerT handler = null;

            using (ISession session = SessionManager.OpenSession())
            {
                using (ITransaction transaction = session.BeginTransaction())
                {
                    try
                    {
                        Logger.InfoFormat(
                            "Trying to resolve handler from db with parameters: type={0}({1}), code={2}({3}), subcode={4}({5})",
                            (int) HandlerTypes.Request, HandlerTypes.Request,
                            operationRequest.OperationCode, (OperationCodes) operationRequest.OperationCode,
                            operationRequest.Parameters[(byte) ParameterCodes.OperationSubCode],
                            (OperationSubCodes) operationRequest.Parameters[(byte) ParameterCodes.OperationSubCode]);

                        handler =
                            session.QueryOver<HandlerT>()
                                .Where(h => h.HandlerType == (int) HandlerTypes.Request)
                                .And(h => h.HandlerCode == operationRequest.OperationCode)
                                .And(
                                    h =>
                                        h.HandlerSubCode == (int?)
                                            operationRequest.Parameters[(byte) ParameterCodes.OperationSubCode])
                                .And(h => h.Application == ServerPeerData.ApplicationId)
                                .And(h => h.ServerHandler == Convert.ToInt32(true))
                                .SingleOrDefault();

                        transaction.Commit();
                    }
                    catch (Exception ex)
                    {
                        Logger.ErrorFormat("Operation failed with: {0}", ex.Message);
                        transaction.Rollback();
                    }
                }

                if (handler != null)
                {
                    Logger.InfoFormat("Found ServerHandler with id={0} on server={1}", handler.HandlerId,
                        handler.ServerId);

                    _serverHandlerRegistry.GetServerHandlerById(Guid.Parse(handler.HandlerId))
                        .HandleTelegram(
                            new RequestTelegram
                            {
                                TelegramCode = operationRequest.OperationCode,
                                TelegramSubCode =
                                    (int) operationRequest.Parameters[(byte) ParameterCodes.OperationSubCode],
                                TelegramParameters = operationRequest.Parameters
                            }, this);
                }
                else
                {
                    Logger.ErrorFormat("No handler found!");
                }

                session.Close();
            }
        }

        protected override void OnDisconnect(DisconnectReason reasonCode, string reasonDetail)
        {
            Logger.InfoFormat("Server disconnected form server {0} (Guid: {1})", ServerPeerData.ApplicationId,
                ServerPeerId);
        }

        protected override void OnEvent(IEventData eventData, SendParameters sendParameters)
        {
            HandlerT handler = null;

            using (ISession session = SessionManager.OpenSession())
            {
                using (ITransaction transaction = session.BeginTransaction())
                {
                    try
                    {
                        Logger.InfoFormat(
                            "Trying to resolve handler from db with parameters: type={0}({1}), code={2}({3}), subcode={4}({5})",
                            (int) HandlerTypes.Event, HandlerTypes.Event,
                            eventData.Code, (OperationCodes) eventData.Code,
                            eventData.Parameters[(byte) ParameterCodes.OperationSubCode],
                            (OperationSubCodes) eventData.Parameters[(byte) ParameterCodes.OperationSubCode]);

                        handler =
                            session.QueryOver<HandlerT>()
                                .Where(h => h.HandlerType == (int) HandlerTypes.Response)
                                .And(h => h.HandlerCode == eventData.Code)
                                .And(
                                    h =>
                                        h.HandlerSubCode == (int?)
                                            eventData.Parameters[(byte) ParameterCodes.OperationSubCode])
                                .And(h => h.ServerHandler == Convert.ToInt32(true))
                                .And(h => h.Application == ServerPeerData.ApplicationId)
                                .SingleOrDefault();

                        transaction.Commit();
                    }
                    catch (Exception ex)
                    {
                        Logger.ErrorFormat("Operation failed with: {0}", ex.Message);
                        transaction.Rollback();
                    }
                }
                session.Close();
            }

            if (handler != null)
            {
                Logger.InfoFormat("Found ServerHandler with id={0} on server={1}", handler.HandlerId,
                    handler.ServerId);

                _serverHandlerRegistry.GetServerHandlerById(Guid.Parse(handler.HandlerId))
                    .HandleTelegram(
                        new EventTelegram
                        {
                            TelegramCode = eventData.Code,
                            TelegramSubCode = (int) eventData.Parameters[(byte) ParameterCodes.OperationSubCode],
                            TelegramParameters = eventData.Parameters
                        }, this);
            }
            else
            {
                Logger.ErrorFormat("No handler found!");
            }
        }

        protected override void OnOperationResponse(OperationResponse operationResponse, SendParameters sendParameters)
        {
            HandlerT handler = null;

            using (ISession session = SessionManager.OpenSession())
            {
                using (ITransaction transaction = session.BeginTransaction())
                {
                    try
                    {
                        Logger.InfoFormat(
                            "Trying to resolve handler from db with parameters: type={0}({1}), code={2}({3}), subcode={4}({5})",
                            (int) HandlerTypes.Response, HandlerTypes.Response,
                            operationResponse.OperationCode, (OperationCodes) operationResponse.OperationCode,
                            operationResponse.Parameters[(byte) ParameterCodes.OperationSubCode],
                            (OperationSubCodes) operationResponse.Parameters[(byte) ParameterCodes.OperationSubCode]);

                        handler =
                            session.QueryOver<HandlerT>()
                                .Where(h => h.HandlerType == (int) HandlerTypes.Response)
                                .And(h => h.HandlerCode == operationResponse.OperationCode)
                                .And(
                                    h =>
                                        h.HandlerSubCode == (int?)
                                            operationResponse.Parameters[(byte) ParameterCodes.OperationSubCode])
                                .And(h => h.ServerHandler == Convert.ToInt32(true))
                                .And(h => h.Application == ServerPeerData.ApplicationId)
                                .SingleOrDefault();

                        transaction.Commit();
                    }
                    catch (Exception ex)
                    {
                        Logger.ErrorFormat("Operation failed with: {0}", ex.Message);
                        transaction.Rollback();
                    }
                }
                session.Close();
            }

            if (handler != null)
            {
                Logger.InfoFormat("Found ServerHandler with id={0} on server={1}", handler.HandlerId,
                    handler.ServerId);

                _serverHandlerRegistry.GetServerHandlerById(Guid.Parse(handler.HandlerId))
                    .HandleTelegram(
                        new ResponseTelegram
                        {
                            TelegramCode = operationResponse.OperationCode,
                            TelegramSubCode = (int) operationResponse.Parameters[(byte) ParameterCodes.OperationSubCode],
                            TelegramParameters = operationResponse.Parameters,
                            ReturnCode = operationResponse.ReturnCode,
                            DebugMessage = operationResponse.DebugMessage
                        }, this);
            }
            else
            {
                Logger.ErrorFormat("No handler found!");
            }
        }

        public void SendTelegram(ITelegram telegram)
        {
            ResponseTelegram response = telegram as ResponseTelegram;

            if (response != null)
            {
                if (!telegram.TelegramParameters.ContainsKey((byte) ParameterCodes.OperationSubCode))
                {
                    telegram.TelegramParameters.Add((byte) ParameterCodes.OperationSubCode, telegram.TelegramSubCode);
                }

                SendOperationResponse(
                    new OperationResponse(telegram.TelegramCode, telegram.TelegramParameters)
                    {
                        DebugMessage = response.DebugMessage,
                        ReturnCode = response.ReturnCode
                    },
                    new SendParameters());
            }
            else if (telegram is RequestTelegram)
            {
                if (!telegram.TelegramParameters.ContainsKey((byte) ParameterCodes.OperationSubCode))
                {
                    telegram.TelegramParameters.Add((byte) ParameterCodes.OperationSubCode, telegram.TelegramSubCode);
                }

                SendOperationRequest(new OperationRequest(telegram.TelegramCode, telegram.TelegramParameters),
                    new SendParameters());
            }
            else if (telegram is EventTelegram)
            {
                SendEvent(new EventData(telegram.TelegramCode, telegram.TelegramParameters), new SendParameters());
            }
        }
    }
}
